package com.qf.controller;

import com.qf.mapper.TicketMapper;
import com.qf.pojo.Ticket;
import com.qf.utils.ThreadUtiils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/ticket")
public class TicketController2 {

    @Autowired
    private TicketMapper ticketMapper;

    @Value("${winnum}")
    private Integer winnum;

    @Autowired
    private StringRedisTemplate redisTemplate;

    //加锁的前缀
    public static final String LOCK_PREFIX = "LOCK:ID:";
    //加锁的值前缀
    public static final String VALUE_PREFIX = "LOCK:VALUE:";


    @GetMapping("/sell/{id}")
    public void sell(@PathVariable("id") Integer id){

        //开启守护线程，给当前进程获取到的锁对象续时(当程序内部所有的线程都是守护线程，守护线程会自动退出)
        // 看门狗 -- watch dog
        Thread thread = new Thread(()->{
            //给锁续时
            while (true){
                //获取锁的超时时间
                Long expire = redisTemplate.getExpire(LOCK_PREFIX + id);
                //获取锁键对应的值
                String value = redisTemplate.opsForValue().get(LOCK_PREFIX + id);
                //① 判断时间 ② 判断是不是我自己加的锁，如果是，我才需要续时
                if (expire <= 2 && value.equals(VALUE_PREFIX + winnum)){
                    //重新续时
                    redisTemplate.expire(LOCK_PREFIX + id,4, TimeUnit.SECONDS);
                }
            }
        });
        thread.setDaemon(true);
        thread.start();


        while (true){
            //此时，在集群环境下需要保障多进程操作共享数据的安全，需要使用【分布式锁】。

            //加锁(setIfAbsent 内部使用的就是setnx指令)

            /*
                问题一：如果当前进程抢占锁后，因为某些原因，导致这个进程直接挂掉，锁没有释放，其他的进程就永远都无法获取锁了--死锁。
                解决方案：设置超时时间，当时间到达后，锁自动释放

                问题二：设置的超时时间比较小，业务执行的时间比较长，此时就会出现业务正在执行过程中，锁被释放了，其他的进程就可以获取到锁
                    和当前进程共同执行业务，会产生并发安全问题 -- 锁失效。
                解决方案：锁续时，在业务执行过程中，要定期给锁重新续时，只有当业务正常执行完后，程序正常释放
                    或者由于服务器挂掉，续时任务结束，超时释放

                    定时任务
                    守护线程

                问题三：使用setnx实现的分布式锁是不可重入的
                    工作中使用redisson框架中的分布式锁，他提供的分布式锁是可重入的，因为他内部使用的是hash结构
                        key field value ---> value就用来记录重入次数
             */
            Boolean isLock = redisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + id, VALUE_PREFIX+winnum,4, TimeUnit.SECONDS);

            if (isLock) {
                try {
                    Ticket ticket = ticketMapper.selectById(id);

                    if (ticket.getCount() > 0) {
                        System.out.println(winnum + "号窗口正在卖出第：" + ticket.getCount() + "张票");

                        //模拟卖票的时间
                        ThreadUtiils.sleep(6000);

                        //更新票数
                        int count = ticket.getCount() - 1;
                        ticket.setCount(count);
                        //更新数据库
                        ticketMapper.updateById(ticket);

                        System.out.println(winnum + "号窗口剩余票数为：" + count);
                    } else {
                        break;
                    }
                }finally {
                    //释放锁(删除key-value)
                    redisTemplate.delete(LOCK_PREFIX + id);
                }
            }
        }
    }
}
